package com.fmy.security.config.filter;

import com.fmy.security.cache.CacheOnline;
import com.fmy.security.cache.Token;
import com.fmy.security.commons.enums.PickType;
import com.fmy.security.commons.enums.RespEnum;
import com.fmy.security.commons.unusual.TokenException;
import com.fmy.security.service.TokenExceptionHander;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * @author Mr.fmy
 * @version 1.0.0
 * @ClassName ConcurrentTokenFilter.java
 * @Description TODO token效验填充
 * @createTime 2019年07月06日 10:37:00
 */
@Slf4j
public class TokenContextHolderFilter extends OncePerRequestFilter {

    private AndRequestMatcher andRequestMatcher;
    private final String BEARER = "Bearer ";
    private String tokenKey = "Authorization";
    private CacheOnline cacheOnline;
    private TokenExceptionHander tokenExceptionHander;
    private Set<String> notUrls = new HashSet<>();
    private Set<String> urls = new HashSet<>();

    public TokenContextHolderFilter(CacheOnline cacheOnline, TokenExceptionHander tokenExceptionHander) {
        this.cacheOnline = cacheOnline;
        this.tokenExceptionHander = tokenExceptionHander;
        notUrls("/login/**").url("/**").buildMatcher();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (requiresAuthentication(request)) {
            try {
                String token = request.getHeader(tokenKey);
                inspectToken(token);
                token = token.replace(BEARER, "");
                pickOut(token);
                securityContext(token);
            } catch (TokenException e) {
                tokenExceptionHander.hander(request, response, e);
                return;
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                return;
            }
        }
        doFilter(request, response, chain);
    }


    protected boolean requiresAuthentication(HttpServletRequest request) {
        if (andRequestMatcher == null) return false;
        return andRequestMatcher.matches(request);
    }

    public TokenContextHolderFilter buildMatcher() {
        List<RequestMatcher> matchers = new ArrayList<>();
        urls.forEach(item -> matchers.add(new AntPathRequestMatcher(item)));
        notUrls.forEach(item -> matchers.add(new NegatedRequestMatcher(new AntPathRequestMatcher(item))));
        andRequestMatcher = new AndRequestMatcher(matchers);
        return this;
    }

    public TokenContextHolderFilter notUrls(String... noturls) {
        this.notUrls.addAll(Arrays.asList(noturls));
        return this;
    }

    public TokenContextHolderFilter url(String... urls) {
        this.urls.addAll(Arrays.asList(urls));
        return this;
    }

    private void inspectToken(String token) {
        if (StringUtils.isEmpty(token) || !token.startsWith(BEARER)) {
            throw new TokenException(RespEnum.TOKEN_NON_CONFORMITY_ERROR);
        }
    }

    public TokenContextHolderFilter setTokenKey(String tokenKey) {
        if (tokenKey.isEmpty()) throw new IllegalArgumentException("token key not empty");
        this.tokenKey = tokenKey;
        return this;
    }

    private void pickOut(String token) {
        //验证该token是否过期
        if (cacheOnline.pickOut(token)) {
            PickType pickType = cacheOnline.pickOutType(token);
            if (PickType.PICK_OUT.equals(pickType)) {
                throw new TokenException(RespEnum.TOKEN_KICK_OUT_ERROR);
            } else {
                throw new TokenException(RespEnum.TOKEN_ELSEWHERE_ERROR);
            }
        }
    }

    private void securityContext(String token) {
        String key = cacheOnline.getKey(token);
        if (StringUtils.isEmpty(key)) throw new TokenException(RespEnum.TOKEN_NOT_CACHE);
        List<Token> tokens = cacheOnline.getTokens(key);
        //随便爬取一个 Authentication 因为都一样
        SecurityContextHolder.getContext().setAuthentication(tokens.get(0).getAuthentication());
    }
}
